<?php

declare(strict_types=1);

namespace ImiApp\ApiServer\Common\Auth;

use Imi\App;
use Imi\Config;
use Imi\JWT\Exception\InvalidTokenException;
use Imi\Log\Log;
use Imi\Util\ClassObject;
use Lcobucci\Clock\FrozenClock;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Validation\Constraint\IdentifiedBy;
use Lcobucci\JWT\Validation\Constraint\IssuedBy;
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
use Lcobucci\JWT\Validation\Constraint\PermittedFor;
use Lcobucci\JWT\Validation\Constraint\RelatedTo;
use Lcobucci\JWT\Validation\Constraint\ValidAt;
use Phpben\Imi\Auth\Contract\TokenContract;
use Imi\JWT\Facade\JWT as ImiJwt;
use Imi\Cache\CacheManager as Cache;
use Psr\SimpleCache\InvalidArgumentException;
use Imi\RequestContext;
use Imi\ConnectionContext;

use ImiApp\ApiServer\Common\Random;
use Imi\Db\Db;
use ImiApp\ApiServer\Common\Token;

use ImiApp\ApiServer\Api\Model\FaUser as User;

class RedisToken implements TokenContract
{




    // 配置
    protected $config;

    // jwt配置
    protected $jwtConfig;

    // imi request
    protected $request;

    protected $_error = '错误';
    protected $id = null;
    protected static $instance = null;
    protected $_logined = false;
    protected $_user = null;
    protected $_token = '';
    //Token默认有效时长
    protected $keeptime = 86400;
    protected $requestUri = '';
    protected $rules = [];
    //默认配置
    protected $options = [];

    public function __construct($config)
    {
        $this->config = $config;
        $this->jwtConfig = Config::get("@app.beans.JWT.list." . $config['jwt']['name']);
        $this->request = RequestContext::get('request');
        $login_keeptime = config('login_keeptime');
        if ($login_keeptime > 0) {
            $this->keeptime = $login_keeptime;
        }
    }

    /**
     * 设置错误信息
     *
     * @param string $error 错误信息
     * @return Auth
     */
    public function setError($error)
    {
        /** @var \Imi\Server\Http\Message\Contract\IHttpResponse $response */
        $response = \Imi\RequestContext::get('response');
        $data = ['code' => -1, 'message' => $error];
        $response->setStatus(200)
            ->setHeader('Access-Control-Allow-Origin', "*")
            ->withBody(new \Imi\Util\Stream\MemoryStream(json_encode($data, JSON_UNESCAPED_UNICODE)))
            ->send();

        $this->_error = $error;
        return $this;
    }

    /**
     * 获取错误信息
     * @return string
     */
    public function getError()
    {
        return $this->_error ? $this->_error : '';
    }

    /**
     * 是否登陆
     * @param string|null $token
     * @return bool
     * @throws InvalidArgumentException
     */
    public function isLogin(?string $token = null): bool
    {
        $token = $token ?: $this->getRequestToken();
        // var_dump("RedisToken isLogin token ", $token);
        if (!$token) {
            // var_dump("RedisToken isLogin token 没有token");
            return false;
        }
        if (Cache::get($this->config['cache'], $this->getTokenName($token)) == 'err') {
            // var_dump("RedisToken isLogin token 没有token数据");
            return false;
        }
        if (ConnectionContext::get($this->getTokenName($token))) {
            // var_dump("RedisToken isLogin token 没有token缓存");
            return true;
        }
        if (!$this->pasreToken($token)) {
            // var_dump("RedisToken isLogin token 解析失败");
            return false;
        }
        return true;
    }

    /**
     * 退出登陆
     * @param string|null $token
     * @return bool
     */
    public function logout(?string $token = null): bool
    {
        $token = $token ?: $this->getRequestToken();
        if (!$token) {
            return false;
        }
        try {
            Cache::set($this->config['cache'], $this->getTokenName($token), 'err', $this->jwtConfig['expires']);
        } catch (InvalidArgumentException $e) {
            Log::error($e->getCode() . ':' . $e->getMessage());
        }
        ConnectionContext::set($this->getTokenName($token), false);
        return true;
    }

    /**
     * 获取Header中token
     * @return string
     */
    public function getRequestToken(): string
    {

        $token = (string)$this->request->getHeaderLine('token');
        // var_dump('RedisToken getRequestToken', $token);
        return $token;

        // return str_replace($this->config['jwt']['prefix'], '', $token);
        // $token = (string)$this->request->getHeaderLine($this->config['jwt']['header_name']);
        // return str_replace($this->config['jwt']['prefix'], '', $token);
    }

    /**
     * 解析Token
     * @param string|null $token
     * @param array $option
     * @return mixed
     * @throws InvalidArgumentException
     * @throws InvalidTokenException
     */
    public function pasreToken(?string $token = null, array $option = [])
    {
        $token = $token ?: $this->getRequestToken();
        if (!$token) {
            // var_dump("RedisToken pasreToken 没有token");
            return false;
        }
        $data = Cache::get($this->config['cache'], $this->getTokenName($token));
        if ($data == 'err') {
            // var_dump("RedisToken pasreToken token", $data);
            return false;
        }
        $this->_user = $data;
        $this->id = $data['id'];
        // var_dump('ids',$this->id);
        // validate
        // try {
        //     $jwt = App::getBean('JWT');
        //     $config = $jwt->getConfig($this->config['jwt']['name']);
        //     $token = $jwt->parseToken($token, $this->config['jwt']['name']);
        //     $configuration = Configuration::forAsymmetricSigner($config->getSignerInstance(), InMemory::plainText($config->getPrivateKey() ?? ''), InMemory::plainText($config->getPublicKey() ?? ''));
        //     $constraints = [];
        //     $config->getId() && $constraints[] = new IdentifiedBy($config->getId());
        //     $config->getIssuer() && $constraints[] = new IssuedBy($config->getIssuer());
        //     $config->getAudience() && $constraints[] = new PermittedFor($config->getAudience());
        //     $config->getSubject() && $constraints[] = new RelatedTo($config->getSubject());
        //     if (class_exists(LooseValidAt::class))
        //     {
        //         $validAtClass = LooseValidAt::class;
        //     }
        //     else
        //     {
        //         $validAtClass = ValidAt::class;
        //     }
        //     $constraints[] = new $validAtClass(new FrozenClock(new \DateTimeImmutable()));
        //     if (!$configuration->validator()->validate($token, ...$constraints)) {
        //         return false;
        //     }
        // } catch (\Exception $e) {
        //     return false;
        // }

        // var_dump("RedisToken pasreToken token 数据", $this->config['cache'], $this->getTokenName($token), $data);
        return $data;
        return $token->claims()->get($config->getDataName());
    }

    /**
     * 获取Token
     * @param mixed $data 存入token的数据
     * @param array $option 配置选项
     * @return string
     */
    public function getToken($data = null, array $option = []): string
    {
        $token = Random::uuid();
        return $token;
        // $token = ImiJwt::getToken($data, $this->config['jwt']['name']);
        // return $token->toString();
    }

    /**
     * 登陆
     * @param string $username 用户名
     * @param string $password 密码
     * @param array $option
     * @return mixed
     */
    public function login(string $username, string $password, array $option = [])
    {
        $model = $this->config['model']['user'];
        $keys = $this->config['settings']['user_keys'];
        $user = $model::find([$keys['username'] => $username]);
        // var_dump($this->config['model']['user']);
        if (!$user) {
            $user = $model::find(['mobile' => $username]);
            if (!$user) {
                $this->setError('帐户不正确' . $username);
                return false;
            }
        }
        if ($user->status != 'normal') {
            $this->setError('账号锁定');
            return false;
        }

        if ($user->password != $this->getEncryptPassword($password, $user->salt)) {
            $this->setError('密码不正确');
            return false;
        }

        // $hasher = new $this->config['settings']['hash'];
        // foreach ($keys as $k => $v) {
        //     $option[$v] = $user->{$v} ?? null;
        // }
        // $checkPassword = $hasher->check($password, $user->{$keys['password']}, $option);
        // if (!$checkPassword) {
        //     return false;
        // }
        // if (!($user->status ?? true)) {
        //     return false;
        // }
        // $token = $this->getToken([
        //     'id' => $user->id,
        //     $keys['username'] => $username
        // ]);
        // unique


        //直接登录会员
        $this->direct($user->id);

        $token = $this->_token;
        //只能一个人登录
        // if ($this->config['unique'] ?? false) {
        if (config('unique_switch') == '1') {
            $lastToken = $user->{$keys['token']};
            $lastToken && $this->logout($lastToken);
            $user->update([$keys['token'] => $token]);
        }
        // 登录log
        if ($this->config['model']['login_log'] ?? false) {
            $logModel = $this->config['model']['login_log'];
            if (method_exists($logModel, 'write')) {
                $logModel::write([
                    'user_id' => $user->id,
                    'username' => $user->{$keys['username']},
                    'password' => "****",
                    'ip' => ip(),
                ]);
            }
        }

        $user['token'] = $this->_token;
        return $user;

        $user = $user->toArray();
        $user['token'] = $token;
        return new \Imi\Util\LazyArrayObject($user);
    }

    /**
     * user信息
     * @param string|null $token
     * @return mixed
     * @throws InvalidArgumentException
     * @throws InvalidTokenException
     */
    public function user(?string $token = null)
    {
        $token = $token ?: $this->getRequestToken();
        if (!$token) {
            return false;
        }
        //使用更新
        $user = Cache::get($this->config['cache'], $this->getTokenName($token));
        if ($user) {
            $user = User::find($user['id']);
            ConnectionContext::set($this->getTokenName($token), $user);
            return  $user;
        }

        //用缓存
        // if ($user = ConnectionContext::get($this->getTokenName($token))) {
        //     return $user;
        // }
        // return (object)$this->pasreToken($token);
    }

    /**
     * 更新
     * @return void
     * @throws InvalidArgumentException
     */
    public function update()
    {
        $token = $this->getRequestToken();
        $user = Cache::get($this->config['cache'], $this->getTokenName($token));
        if ($user) {
            $user = User::find($user['id']);
            ConnectionContext::set($this->getTokenName($token), $user);
            return  $user;
        }
        return 0;
    }



    /**
     * 获取Token名称
     * @param $token
     * @return string
     */
    public function getTokenName($token): string
    {
        return $token;
        return 'authtoken-' . md5($token);
    }

    /**
     * 检查用户组规则
     * @param array|string $rules 需要检查的规则列表
     * @param int|null $user_id 用户ID
     * @param string $relation or判断其中一条 and检查所有规则
     * @return bool
     * @throws InvalidArgumentException
     * @throws InvalidTokenException
     */
    public function check($rules, ?int $user_id = null, string $relation = 'or'): bool
    {
        is_string($rules) && $rules = [$rules];
        !$user_id && $user_id = $this->user()->id ?? null;
        if (!$user_id) {
            return false;
        }
        $auth = Auth::instance($this->config)->setUserId($user_id);
        $rulelist = $auth->getRuleList(true);
        if (in_array('*', $rulelist)) {
            return true;
        }
        $list = [];
        foreach ($rulelist as $rule) {
            if (in_array($rule, $rules)) {
                $list[] = $rule;
            }
        }
        if ('or' == $relation && !empty($list)) {
            return true;
        }
        $diff = array_diff($rules, $list);
        if ('and' == $relation && empty($diff)) {
            return true;
        }
        return false;
    }

    /**
     * 获取密码加密后的字符串
     * @param string $password 密码
     * @param string $salt     密码盐
     * @return string
     */
    public function getEncryptPassword($password, $salt = '')
    {
        return md5(md5($password) . $salt);
    }



    /**
     * 直接登录账号
     * @param int $user_id
     * @return boolean
     */
    public function direct($user_id)
    {
        $model = $this->config['model']['user'];
        $user = $model::find($user_id);
        if ($user) {

            Db::getInstance()->beginTransaction();


            /** @var \Imi\Server\Http\Message\Contract\IHttpRequest $request */
            $this->request = \Imi\RequestContext::get('request');

            $address = $this->request->getClientAddress();
            try {
                $ip = $address->getAddress();
                $time = time();

                //判断连续登录和最大连续登录
                if ($user->logintime < \ImiApp\ApiServer\Common\Date::unixtime('day')) {
                    $user->successions = $user->logintime < \ImiApp\ApiServer\Common\Date::unixtime('day', -1) ? 1 : $user->successions + 1;
                    $user->maxsuccessions = max($user->successions, $user->maxsuccessions);
                }

                $user->prevtime = $user->logintime;
                //记录本次登录的IP和时间
                $user->loginip = $ip;
                $user->logintime = $time;
                //重置登录失败次数
                $user->loginfailure = 0;





                $this->_token = Random::uuid();
                $user->token = $this->_token;


                $user->save();
                $this->_user = $user;
                // var_dump('direct $this->_token',$this->_token);
                ConnectionContext::set($this->getTokenName($this->_token), $user);
                Token::set($this->_token, $user->id, $this->keeptime);
                // var_dump('AuthApi login token', $this->_token);
                // \Imi\RequestContext::set($this->_token,$user);
                // ConnectionContext::set($this->_token,$user);
                Cache::set('redis', $this->_token, $user, $this->keeptime);
                $this->_logined = true;

                //登录成功的事件
                // Hook::listen("user_login_successed", $this->_user);
                Db::getInstance()->commit();
            } catch (Exception $e) {
                Db::getInstance()->rollBack();
                $this->setError($e->getMessage());
                return false;
            }

            return $user;
            return true;
        } else {
            return false;
        }
    }

    /**
     * 获取User模型
     * @return User
     */
    public function getUser()
    {
        return $this->_user;
    }

    /**
     * 获取会员基本信息
     */
    public function getUserinfo(?string $token = null)
    {
        $token = $token ?: $this->getRequestToken();
        if (!$token) {
            return false;
        }

        if ($user = ConnectionContext::get($this->getTokenName($token))) {
            return $user;
        }
        return (object)$this->pasreToken($token);
    }

    /**
     * 用户id
     * @param string|null $token
     * @return int
     * @throws InvalidArgumentException
     */
    public function id(?string $token = null)
    {
        $token = $token ?: $this->getRequestToken();
        $user = Cache::get($this->config['cache'], $this->getTokenName($token));
        if ($user) {
            return $user['id'];
        }
        return 0;
        return $this->id;
    }

    /**
     * 角色id
     * @return int
     * @throws InvalidArgumentException
     */
    public function set_id()
    {
        $token = $this->getRequestToken();
        $user = Cache::get($this->config['cache'], $this->getTokenName($token));
        if ($user) {
            $user = User::find($user['id']);
            ConnectionContext::set($this->getTokenName($token), $user);
            return  $user['set_id'];
        }
        return 0;
    }


    /**
     * 用户钱
     * @return float
     * @throws InvalidArgumentException
     */
    public function money()
    {
        $token = $this->getRequestToken();
        $user = Cache::get($this->config['cache'], $this->getTokenName($token));
        if ($user) {
            $user = User::find($user['id']);
            ConnectionContext::set($this->getTokenName($token), $user);
            return  $user['money'];
        }
        return 0;
    }

    /**
     * 用户保证金
     * @return float
     * @throws InvalidArgumentException
     */
    public function surety()
    {
        $token = $this->getRequestToken();
        $user = Cache::get($this->config['cache'], $this->getTokenName($token));
        if ($user) {
            $user = User::find($user['id']);
            ConnectionContext::set($this->getTokenName($token), $user);
            return  $user['surety'];
        }
        return 0;
    }

    /**
     * 用户贝壳
     * @return float
     * @throws InvalidArgumentException
     */
    public function shell()
    {
        $token = $this->getRequestToken();
        $user = Cache::get($this->config['cache'], $this->getTokenName($token));
        if ($user) {
            $user = User::find($user['id']);
            ConnectionContext::set($this->getTokenName($token), $user);
            return  $user['shell'];
        }
        return 0;
        return $this->id;
    }



    /**
     * 用户积分
     * @return float
     * @throws InvalidArgumentException
     */
    public function score()
    {
        $token = $this->getRequestToken();
        $user = Cache::get($this->config['cache'], $this->getTokenName($token));
        if ($user) {
            $user = User::find($user['id']);
            ConnectionContext::set($this->getTokenName($token), $user);
            return  $user['score'];
        }
        return 0;
        return $this->id;
    }

    // /**
    //  * 获取当前Token
    //  * @return string
    //  */
    // public function getToken()
    // {
    //     return $this->_token;
    // }

    /**
     * 获取当前Token
     * @return string
     */
    public function Token()
    {
        return $this->getRequestToken();
    }


    /**
     * 注册用户
     *
     * @param string $username 用户名
     * @param string $password 密码
     * @param string $email    邮箱
     * @param string $mobile   手机号
     * @param array  $extend   扩展参数
     * @return boolean
     */
    public function register($username, $password, $email = '', $mobile = '', $extend = [])
    {

        $USER = User::query();
        // 检测用户名、昵称、邮箱、手机号是否存在
        if ($USER->where('username', '=', $username)->count()) {
            $this->setError('用户名已存在');
            return false;
        }
        if ($email && $USER->where('email', '=', $email)->count()) {
            $this->setError('邮箱已存在');
            return false;
        }
        if ($mobile && $USER->where('mobile', '=', $mobile)->count()) {
            $this->setError('手机号已存在');
            return false;
        }

        $ip = ip();
        $time = time();

        $data = [
            'username' => $username,
            'password' => $password,
            'email'    => $email,
            'mobile'   => $mobile,
            'level'    => 1,
            'score'    => 0,
            'avatar'   => '',
        ];
        $params = array_merge($data, [
            'nickname'  => preg_match("/^1[3-9]{1}\d{9}$/", $username) ? substr_replace($username, '****', 3, 4) : $username,
            'salt'      => Random::alnum(),
            'jointime'  => $time,
            'createtime'=> $time,
            'joinip'    => $ip,
            'logintime' => $time,
            'loginip'   => $ip,
            'prevtime'  => $time,
            'status'    => 'normal'
        ]);
        $params['password'] = $this->getEncryptPassword($password, $params['salt']);
        $params = array_merge($params, $extend);

        //账号注册时需要开启事务,避免出现垃圾数据
        Db::getInstance()->beginTransaction();
        try {
            $user = (new User);
            $user->insert($params);

            $this->_user = User::find($user->id);

            //设置Token
            $this->_token = Random::uuid();
            Token::set($this->_token, $user->id, $this->keeptime);

            //设置登录状态
            $this->_logined = true;

            // $tpmecms = \Imi\App::newInstance('ImiApp\ApiServer\Api\Controller\Tpmecmscom');
            Db::getInstance()->commit();
        } catch (Exception $e) {
            $this->setError($e->getMessage());
            Db::getInstance()->rollBack();
            return false;
        }
        return $user;
    }
}
